﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Forums.Services
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Text;
	using Falcon.Core.Collections;
	using Falcon.Core.Domain.Services;
	using Falcon.Core.Extensions;
	using Falcon.Forums.Domain;
	using Falcon.Forums.Domain.Enums;
	using Falcon.Forums.Domain.Repositories;
	using Falcon.Forums.Domain.Services;
	using Falcon.Forums.Domain.Values;

	public class ForumPostService : IForumPostService
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ForumPostService"/> class.
		/// </summary>
		/// <param name="identityService">The identity service.</param>
		/// <param name="logService">The log service.</param>
		/// <param name="forumService">The forum service.</param>
		/// <param name="forumTopicSubscriptionService">The forum topic subscription service.</param>
		/// <param name="forumPostAttachmentService">The forum post attachment service.</param>
		/// <param name="forumTopicRepository">The forum topic repository.</param>
		/// <param name="forumPostRepository">The forum post repository.</param>
		public ForumPostService(
			IIdentityService identityService,
			ILogService logService,
			IForumService forumService,
			IForumTopicSubscriptionService forumTopicSubscriptionService, 
			IForumPostAttachmentService forumPostAttachmentService,
			IForumTopicRepository forumTopicRepository,
			IForumPostRepository forumPostRepository)
		{
			this.IdentityService = identityService;
			this.LogService = logService;
			this.ForumService = forumService;
			this.ForumTopicSubscriptionService = forumTopicSubscriptionService;
			this.ForumPostAttachmentService = forumPostAttachmentService;
			this.ForumTopicRepository = forumTopicRepository;
			this.ForumPostRepository = forumPostRepository;
		}

		/// <summary>
		/// Gets or sets the identity service.
		/// </summary>
		/// <value>The identity service.</value>
		public IIdentityService IdentityService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the log service.
		/// </summary>
		/// <value>The log service.</value>
		public ILogService LogService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum service.
		/// </summary>
		/// <value>The forum service.</value>
		public IForumService ForumService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum topic subscription service.
		/// </summary>
		/// <value>The forum topic subscription service.</value>
		public IForumTopicSubscriptionService ForumTopicSubscriptionService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum post attachment service.
		/// </summary>
		/// <value>The forum post attachment service.</value>
		public IForumPostAttachmentService ForumPostAttachmentService
		{
			get; 
			private set;
		}

		/// <summary>
		/// Gets or sets the forum topic repository.
		/// </summary>
		/// <value>The forum topic repository.</value>
		public IForumTopicRepository ForumTopicRepository
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum post repository.
		/// </summary>
		/// <value>The forum post repository.</value>
		public IForumPostRepository ForumPostRepository
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the forum post by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public ForumPost GetBy(ForumPostSpecification specification)
		{
			return this.ForumPostRepository.GetBy(specification);
		}

		/// <summary>
		/// Gets the forum post by id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public ForumPost GetById(int id)
		{
			return this.ForumPostRepository.GetById(id);
		}

		public IPagedList<ForumPost, ForumPostSpecification> GetPaged(ForumPostSpecification specification)
		{
			var posts = this.ForumPostRepository.GetPaged(specification);

			var attachments = this.ForumPostAttachmentService.GetAll(posts.Select(x => x.Id).ToArray());

			posts.ForEach(x => x.Attachments = attachments.ContainsKey(x.Id) ? attachments[x.Id] : null);

			return posts;
		}

		/// <summary>
		/// Creates the forum post.
		/// </summary>
		/// <returns></returns>
		public ForumPost Create()
		{
			var post = new ForumPost
			{
				Created = DateTime.Now
			};

			post.Updated = post.Created;

			return post;
		}

		/// <summary>
		/// Inserts the forum post into the storage.
		/// </summary>
		/// <param name="post">The <see cref="ForumPost"/>.</param>
		public void Insert(ForumPost post)
		{
			post.Validate();

			post.Slug = post.Title.Slug();
			post.BodyParsed = post.Body.BBCodeToHtml(true);

			this.ForumPostRepository.Insert(post);

			var forum = ForumService.GetById(post.ForumId);

			forum.LastPostId = post.Id;
			forum.Updated = post.Created;
			forum.Total.UpdatePosts();

			this.ForumService.Update(forum);
		}

		/// <summary>
		/// Inserts the specified post.
		/// </summary>
		/// <param name="post">The post.</param>
		/// <param name="value">The value.</param>
		public void Insert(ForumPost post, ForumPostCreate value)
		{
			value.Validate();

			var identity = IdentityService.Current;

			post.ForumId = value.ForumId;
			post.TopicId = value.TopicId;
			post.UserId = identity.Id;
			post.ParentId = value.ParentId;
			post.Title = value.Title;
			post.Body = value.Body;

			this.Insert(post);

			this.ForumPostAttachmentService.Insert(post, value.Attachments);

			if (value.Notification == ForumTopicSubscriptionNotification.SubscribeDisabled)
			{
				return;
			}

			var subscription = this.ForumTopicSubscriptionService.GetBy(new ForumTopicSubscriptionSpecification
			{
				TopicId = post.TopicId,
				UserId = identity.Id
			});

			if (subscription != null)
			{
				return;
			}

			subscription = this.ForumTopicSubscriptionService.Create();

			subscription.TopicId = post.TopicId;
			subscription.UserId = identity.Id;
			subscription.Notification = value.Notification;

			this.ForumTopicSubscriptionService.Insert(subscription);
		}

		/// <summary>
		/// Updates the specified post.
		/// </summary>
		/// <param name="post">The post.</param>
		public void Update(ForumPost post)
		{
			post.Validate();

			post.Slug = post.Title.Slug();
			post.BodyParsed = post.Body.BBCodeToHtml(true);
			post.Updated = DateTime.Now;

			this.ForumPostRepository.Update(post);
		}

		/// <summary>
		/// Updates the specified post.
		/// </summary>
		/// <param name="post">The post.</param>
		/// <param name="value">The value.</param>
		public void Update(ForumPost post, ForumPostUpdate value)
		{
			value.Validate();

			post.Title = value.Title;
			post.Body = value.Body;

			this.Update(post);
		}

		/// <summary>
		/// Updates the specified posts.
		/// </summary>
		/// <param name="posts">The posts.</param>
		/// <param name="action">The action.</param>
		public void Update(IList<ForumPost> posts, ForumPostAction action)
		{
			if (action != ForumPostAction.UnDelete)
			{
				return;
			}

			foreach (var post in posts)
			{
				var log = this.LogService.Log(Logs.UpdatedPostStatusToPublished.Fmt(post.Id));

				post.LogId = log.Id;
				post.Status = ForumPostStatus.Published;

				this.Update(post);
			}
		}

		/// <summary>
		/// Deletes the specified post.
		/// </summary>
		/// <param name="post">The post.</param>
		public void Delete(ForumPost post)
		{
			this.ForumPostRepository.Delete(post);
		}

		public void Delete(ForumPostDelete value)
		{
			this.Delete(value.PostsToDelete, value.Reason);
		}

		public void Delete(ForumPostDeleteSpam value)
		{
			var deleted = this.Delete(value.PostsToDelete, value.Reason);

			foreach (var post in deleted)
			{
				if (value.Ban)
				{
					// ban user
				}

				if (!value.Purge)
				{
					continue;
				}

				var posts = this.GetPaged(new ForumPostSpecification
				{
					UserId = post.User.Id
				});

				this.Delete(posts.Items, value.Reason);
			}
		}

		public void Move(ForumPostMove value)
		{
			value.Validate();

			if (value.PostsToMove == null)
			{
				return;
			}

			if (value.Action == ForumPostMoveAction.Create)
			{
				var topic = new ForumTopic
				{
					ForumId = value.ForumId,
					Slug = value.Title.Slug(),
					Replies = 0,
					Views = 0,
					Created = DateTime.Now
				};

				topic.Updated = topic.Created;

				this.ForumTopicRepository.Insert(topic);

				var post = this.GetById(value.PostsToMove[0].Id);

				post.ForumId = value.ForumId;
				post.TopicId = topic.Id;
				post.ParentId = 0;
				post.Title = value.Title;

				this.Update(post);

				topic.PostId = post.Id;

				this.ForumTopicRepository.Update(topic);

				for (var i = 0; i < value.PostsToMove.Length; i++)
				{
					if (post.Id.Equals(value.PostsToMove[i].Id))
					{
						continue;
					}

					var child = this.GetById(value.PostsToMove[i].Id);

					if (child == null)
					{
						continue;
					}

					child.ForumId = value.ForumId;
					child.TopicId = topic.Id;
					child.ParentId = post.Id;

					this.Update(child);
				}
			}
			else
			{
				var topic = this.ForumTopicRepository.GetById(value.TopicId);

				if (topic == null)
				{
					return;
				}

				for (var i = 0; i < value.PostsToMove.Length; i++)
				{
					var post = this.GetById(value.PostsToMove[i].Id);

					if (post == null)
					{
						continue;
					}

					if (post.ParentId <= 0)
					{
						var previous = this.ForumTopicRepository.GetById(post.TopicId);
						var posts = this.GetPaged(new ForumPostSpecification { TopicId = previous.Id });

						var existing = new List<ForumPost>();

						foreach (var postOfprevious in posts)
						{
							var ofprevious = postOfprevious;

							var count = value.PostsToMove.Where(x => x.Id == ofprevious.Id).Count();

							if (count == 0)
							{
								existing.Add(ofprevious);
							}
						}

						if (existing.Count > 0)
						{
							var first = existing.FirstOrDefault();

							first.ParentId = 0;

							this.Update(first);

							previous.PostId = first.Id;

							var last = existing.OrderByDescending(x => x.Created).FirstOrDefault();

							if (last.Id != previous.PostId)
							{
								previous.LastPostId = last.Id;
							}

							ForumTopicRepository.Update(previous);

							var postsOfPrevious = existing.Where(x => x.Id != first.Id).ToList();

							foreach (var postOfprevious in postsOfPrevious)
							{
								postOfprevious.ParentId = first.Id;
								this.Update(postOfprevious);
							}
						}
						else
						{
							ForumTopicRepository.Delete(previous);
						}
					}

					post.ForumId = topic.ForumId;
					post.TopicId = topic.Id;
					post.ParentId = topic.PostId;

					this.Update(post);

					if (topic.Post.Created >= post.Created)
					{
						continue;
					}

					topic.LastPost = post;

					ForumTopicRepository.Update(topic);
				}
			}
		}

		public void Merge(ForumPostMerge value)
		{
			if (value.PostsToMerge == null)
			{
				return;	
			}

			var post = this.GetById(value.PostsToMerge[0].Id);

			post.UserId = value.UserId;
			post.Title = value.Title;
			post.Body = value.Body;

			for (var i = 0; i < value.PostsToMerge.Length; i++)
			{
				if (post.Id.Equals(value.PostsToMerge[i].Id))
				{
					continue;
				}

				var child = this.GetById(value.PostsToMerge[i].Id);

				if (child == null)
				{
					continue;
				}

				this.Delete(child);
			}

			this.Update(post);
		}

		public void Copy(ForumPostCopy value)
		{
			value.Validate();

			if (value.PostsToCopy == null)
			{
				return;
			}

			if (value.Action == ForumPostCopyAction.Create)
			{
				var topic = new ForumTopic
				{
					ForumId = value.ForumId,
					Slug = value.Title.Slug(),
					Replies = 0,
					Views = 0,
					Created = DateTime.Now
				};

				topic.Updated = topic.Created;

				this.ForumTopicRepository.Insert(topic);

				var post = this.GetById(value.PostsToCopy[0].Id);
				var copyOfpost = this.Create();

				copyOfpost.ForumId = value.ForumId;
				copyOfpost.TopicId = topic.Id;
				copyOfpost.UserId = post.UserId;
				copyOfpost.Title = value.Title;
				copyOfpost.Body = post.Body;

				this.Insert(copyOfpost);

				topic.PostId = copyOfpost.Id;

				this.ForumTopicRepository.Update(topic);

				for (var i = 0; i < value.PostsToCopy.Length; i++)
				{
					if (post.Id.Equals(value.PostsToCopy[i].Id))
					{
						continue;
					}

					var child = this.GetById(value.PostsToCopy[i].Id);

					if (child == null)
					{
						continue;
					}

					var copyOfchild = this.Create();

					copyOfchild.ForumId = value.ForumId;
					copyOfchild.TopicId = topic.Id;
					copyOfchild.ParentId = copyOfpost.Id;
					copyOfchild.UserId = child.UserId;
					copyOfchild.Title = child.Title;
					copyOfchild.Body = child.Body;

					this.Insert(copyOfchild);
				}
			}
			else
			{
				var topic = this.ForumTopicRepository.GetById(value.TopicId);

				if (topic == null)
				{
					return;
				}

				for (var i = 0; i < value.PostsToCopy.Length; i++)
				{
					var post = this.GetById(value.PostsToCopy[i].Id);

					if (post == null)
					{
						continue;
					}

					var copyOfpost = this.Create();

					copyOfpost.ForumId = post.ForumId;
					copyOfpost.TopicId = topic.Id;
					copyOfpost.ParentId = topic.PostId;
					copyOfpost.UserId = post.UserId;
					copyOfpost.Title = post.Title;
					copyOfpost.Body = post.Body;

					this.Insert(copyOfpost);

					if (topic.Post.Created >= copyOfpost.Created)
					{
						continue;
					}

					topic.LastPost = copyOfpost;

					ForumTopicRepository.Update(topic);
				}
			}
		}

		/// <summary>
		/// Deletes the specified post.
		/// </summary>
		/// <param name="posts">The posts.</param>
		/// <param name="reason">The reason.</param>
		public void Delete(IList<ForumPost> posts, string reason)
		{
			foreach (var post in posts)
			{
				if (post == null)
				{
					continue;
				}

				var log = this.LogService.Create();

				log.UserId = IdentityService.Current.Id;
				log.Action = Logs.UpdatedPostStatusToDeleted.Fmt(post.Id);
				log.Reason = reason;

				this.LogService.Insert(log);

				post.LogId = log.Id;
				post.Status = ForumPostStatus.Deleted;

				this.Update(post);

				if (post.ParentId != 0)
				{
					continue;
				}

				var topic = this.ForumTopicRepository.GetById(post.TopicId);

				topic.LogId = log.Id;
				topic.Status = ForumTopicStatus.Deleted;

				this.ForumTopicRepository.Update(topic);
			}
		}

		/// <summary>
		/// Transforms the specified values that are selected to the posts list.
		/// </summary>
		/// <param name="values">The values.</param>
		/// <param name="posts">The posts.</param>
		public void Transform(ForumPostUpdateValues[] values, IList<ForumPost> posts)
		{
			if (values == null)
			{
				return;
			}

			for (var i = 0; i < values.Length; i++)
			{
				if (!values[i].Selected)
				{
					continue;
				}

				var post = this.GetById(values[i].Id);

				if (post == null)
				{
					continue;
				}

				posts.Add(post);
			}
		}

		private IList<ForumPost> Delete(ForumPostUpdateValues[] values, string reason)
		{
			var deleted = new List<ForumPost>();
			var identity = this.IdentityService.Current;

			if (values == null)
			{
				return deleted;
			}

			for (var i = 0; i < values.Length; i++)
			{
				var post = this.ForumPostRepository.GetById(values[i].Id);

				if (post == null)
				{
					continue;
				}

				var log = this.LogService.Log(Logs.UpdatedPostStatusToDeleted.Fmt(post.Id), reason);

				post.LogId = log.Id;
				post.Status = ForumPostStatus.Deleted;

				this.ForumPostRepository.Update(post);

				deleted.Add(post);
			}

			return deleted;
		}
	}
}